<?php
require_once "utils.php";

class PathDriverAliyun extends PathDriverBase
{
    protected $accessToken = '';
    protected $name = '';    // 存储名称，用于刷新token
    protected $expireTime = '';    //token过期时间
    protected $defaultDriveId = '';    //设备号
    protected $rootDir = '';    //根目录
    protected $gaosuSize = '';    //极速模式数量
    protected $asyncNum = '';    //异步数量

    public function __construct($config)
    {
        parent::__construct();
        $this->_init($config);
        $this->checkToken();
    }

    // 初始化配置信息
    private function _init($data = array())
    {
        $list = array(
            'accessToken' => 'access_token',
            'name' => 'name',
            'expireTime' => 'expire_time',
            'defaultDriveId' => 'default_drive_id',
            'gaosuSize' => 'gaosu_size',
            'asyncNum' => 'async_num'
        );
        foreach ($list as $key => $name) {
            if (!isset($data[$name])) {
                show_json('请去存储管理-阿里云盘-修改配置强制修改并保存', false);
            }
            $this->$key = $data[$name];
        }
        $dir = dirname(__FILE__) . "_file_" . $this->defaultDriveId;
        $dir = str_replace('\\', '/', $dir);
        $this->rootDir = $dir;
        if (!is_dir($dir)) {
            mkdir($dir, 0777, true);
        }
        set_token($this->rootDir, array("access_token" => $this->accessToken, "expire_time" => $this->expireTime));
    }

    // 检查accessToken
    public function checkToken()
    {
        if (strtotime($this->expireTime) > time() + 1800) return;
        $data = Hook::trigger("aliyun.refresh.token", $this->name);
        if (!empty($data)) $this->_init($data);
        logOut("checkToken:" . json_encode($data));
    }

    /**
     * 用户信息
     * @return void
     */
    public function userInfo()
    {
        logOut("userInfo:");
        return aliRequest($this->rootDir, 'rest/2.0/xpan/nas?method=uinfo');
    }

    /**
     * 网盘容量信息
     * https://pan.aliyun.com/union/document/basic#获取网盘容量信息
     * {"errno":0,"used":1720839525296,"total":2206539448320,"request_id":9048281600181199460}
     * @return void
     */
    public function diskInfo()
    {
        logOut("diskInfo:");
        $data = aliRequest($this->rootDir, 'api/quota');
        return array('total' => $data['total'], 'used' => $data['used']);
    }

    /**
     * 创建文件    本地生成+上传
     * @param type $path
     * @param type $name
     * @return boolean
     */
    public function mkfile($path, $content = '', $repeat = REPEAT_RENAME)
    {
        logOut("mkfile:" . json_encode(array(
                "path" => $path,
                "content" => $content,
                "repeat" => $repeat,
            )));
        if ($this->setContent($path, $content, $repeat)) {
            return $this->getPathOuter($path);
        }
        return false;
    }

    /**
     * 创建文件夹
     * @param type $path
     * @param type $name
     * @return boolean
     */
    public function mkdir($dir, $repeat = REPEAT_SKIP)
    {
        logOut("mkdir:" . json_encode(array(
                "dir" => $dir,
                "repeat" => $repeat,
            )));
        $fl = fenli_name($dir);
        $pathParent = $fl['parentPath'];
        $pathName = $fl['currentPath'];
        $pid = path2id($this->rootDir, $pathParent);
        $param = array(
            "parent_file_id" => $pid,
            "drive_id" => $this->defaultDriveId,
            "name" => $pathName,
            "check_name_mode" => "refuse",
            "type" => "folder"
        );
        $headers = array("Content-Type: application/json",
            "authorization: Bearer " . $this->accessToken,
            "cache-control: no-cache");
        aliRequest($this->rootDir, '/adrive/v2/file/createWithFolders', $param, $headers);
        $path = $this->getPathOuter($dir);
        $this->sxLastFile($pathParent);
        return $path;
    }

    private function aliRename($file_id, $name)
    {
        $param = array("file_id" => $file_id, "drive_id" => $this->defaultDriveId, "name" => $name, "check_name_mode" => "refuse");
        $headers = array("Content-Type: application/json",
            "authorization: Bearer " . $this->accessToken,
            "cache-control: no-cache");
        $data = aliRequest($this->rootDir, '/v3/file/update', $param, $headers);
        return $data && $data['file_id'];
    }

    private function utilsPost($type, $data)
    {
        $curl = curl_init();
        $url = $this->getUtilPhp() . "?type=" . $type;
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($curl, CURLOPT_POST, 1);
        curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
        curl_setopt($curl, CURLOPT_HTTPHEADER, array("Content-Type: application/json"));
        curl_setopt($curl, CURLOPT_HEADER, 0);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl, CURLOPT_TIMEOUT, 1);
        $res = curl_exec($curl);
        curl_close($curl);
        return $res;
    }

    private function sxLastFile($path)
    {
        if ($path == "/") {
            $path = "";
        }
        $data = array(
            "path" => $path,
            "rootDir" => $this->rootDir,
            "driveId" => $this->defaultDriveId,
            "accessToken" => $this->accessToken
        );
        $count = path_file_size($data['rootDir'], $data['path']);
        //默认开启异步模式
        $async = "1";
        if ($this->asyncNum == "0") {
            //为0使用同步模式
            $async = "0";
        } else {
            if ($count <= $this->asyncNum) {
                //数量小于预设使用同步模式
                $async = "0";
            }
        }
        $this->utilsPost("2&async=" . $async, $data);
    }

    /**
     * 文件管理：copy/move/rename/delete
     * https://pan.aliyun.com/union/document/basic#%E7%AE%A1%E7%90%86%E6%96%87%E4%BB%B6
     * @return void
     */
    private function fileManager($fileList, $action)
    {
        logOut("fileManager:" . json_encode(array(
                "fileList" => $fileList,
                "action" => $action,
            )));
        if ($action == "rename") {
            $file_id = path2id($this->rootDir, $fileList[0]['path']);
            $name = $fileList[0]['newname'];
            $flag = $this->aliRename($file_id, $name);
            $this->sxLastFile(fenli_name($fileList[0]['path'])['parentPath']);
            return $flag;
        } else if ($action == "delete") {
            $file_id = path2id($this->rootDir, $fileList[0]);
            $param = array("file_id" => $file_id, "drive_id" => $this->defaultDriveId);
            $headers = array("Content-Type: application/json",
                "authorization: Bearer " . $this->accessToken,
                "cache-control: no-cache");
            aliRequest($this->rootDir, '/v2/recyclebin/trash', $param, $headers);
            $this->sxLastFile(fenli_name($fileList[0])['parentPath']);
            deldir($this->rootDir . $fileList[0]);
            return true;
        } else if ($action == "move" || $action == "copy") {
            $from_file_id = path2id($this->rootDir, $fileList[0]['path']);
            $to_file_id = path2id($this->rootDir, $fileList[0]['dest']);
            $oldname = fenli_name($fileList[0]['path'])['currentPath'];
            $newname = $fileList[0]['newname'];
            $param = array(
                "requests" => array(
                    0 => array(
                        "body" => array(
                            "drive_id" => $this->defaultDriveId,
                            "file_id" => $from_file_id,
                            "to_drive_id" => $this->defaultDriveId,
                            "to_parent_file_id" => $to_file_id
                        ),
                        "headers" => array(
                            "Content-Type" => "application/json"
                        ),
                        "id" => $from_file_id,
                        "method" => "POST",
                        "url" => "/file/copy"
                    )
                ),
                "resource" => "file"
            );
            $headers = array("Content-Type: application/json",
                "authorization: Bearer " . $this->accessToken,
                "cache-control: no-cache");
            $res = aliRequest($this->rootDir, '/v3/batch', $param, $headers);
            if ($newname != $oldname) {
                //重命名
                $file_id = $res["responses"][0]["id"];
                $this->aliRename($file_id, $newname);
            }
            $this->sxLastFile(fenli_name($fileList[0]['path'])['parentPath']);
            $this->sxLastFile($fileList[0]['dest']);
            return true;
        }
        return false;
    }

    /**
     * 复制
     * @param type $from
     * @param type $to
     * @return boolean
     */
    public function copyFile($from, $to)
    {
        logOut("copyFile:" . json_encode(array(
                "from" => $from,
                "to" => $to,
            )));
        $dest = get_path_father($to);
        $fileList = array(
            'path' => $from,
            'dest' => '/' . trim($dest, '/'),
            'newname' => get_path_this($to)
        );
        return $this->fileManager(array($fileList), 'copy');
    }

    /**
     * 移动
     * @param type $from
     * @param type $to
     * @return boolean
     */
    public function moveFile($from, $to)
    {
        logOut("moveFile:" . json_encode(array(
                "from" => $from,
                "to" => $to,
            )));
        $dest = get_path_father($to);
        $fileList = array(
            'path' => $from,
            'dest' => '/' . trim($dest, '/'),
            'newname' => get_path_this($to)
        );
        return $this->fileManager(array($fileList), 'move');
    }

    /**
     * 删除文件(文件夹需要加'/')
     * @param type $path
     * @return boolean
     */
    public function delFile($path)
    {
        logOut("delFile:" . json_encode(array(
                "path" => $path
            )));
        return $this->fileManager(array($path), 'delete');
    }

    /**
     * 删除文件夹，和删除文件相同（同一目录下不会存在文件/夹名相同的情况）
     * @param type $path
     * @return boolean
     */
    public function delFolder($path)
    {
        logOut("delFolder:" . json_encode(array(
                "path" => $path
            )));
        return $this->delFile($path);
    }

    /**
     * 重命名
     * @param type $from
     * @param type $to
     * @return type
     */
    public function rename($from, $to)
    {
        logOut("rename:" . json_encode(array(
                "from" => $from,
                "to" => $to
            )));
        $fileList = array(
            'path' => $from,
            'newname' => get_path_this($to)
        );
        return $this->fileManager(array($fileList), 'rename');
    }

    private function getDownUrl($file_id)
    {
        logOut("getDownUrl:" . json_encode(array(
                "file_id" => $file_id
            )));
        $param = array("file_id" => $file_id, "drive_id" => $this->defaultDriveId);
        $headers = array("Content-Type: application/json",
            "authorization: Bearer " . $this->accessToken,
            "cache-control: no-cache");
        $data = aliRequest($this->rootDir, '/v2/file/get_download_url', $param, $headers);
        return $data['url'];
    }

    private function getPluginsPath()
    {
        $tmpUrl = "http://";
        if (is_https()) {
            $tmpUrl = "https://";
        }
        $tmpUrl = $tmpUrl . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
        $tmpUrl = explode("?", $tmpUrl)[0];
        if (strrpos($tmpUrl, "index.php")) {
            $tmpUrl = substr($tmpUrl, 0, strrpos($tmpUrl, "index.php"));
        }
        $localPath = dirname(__FILE__);
        $localPath = str_replace('\\', '/', $localPath);
        $pathSz = explode("/", $localPath);
        $pathCount = count($pathSz);
        $tmpUrl = $tmpUrl . "plugins/" . $pathSz[$pathCount - 2];
        return $tmpUrl;
    }

    private function getUtilPhp()
    {
        $tmpUrl = $this->getPluginsPath();
        $tmpUrl = $tmpUrl . "/lib/utils.php";
        return $tmpUrl;
    }

    private function getRefererUrl($url, $fileName, $size)
    {
        logOut("getRefererUrl:".json_encode(
            array(
                "url"=>$url,
                "fileName"=>$fileName,
                "size"=>$size
            )
            ));
        $tmpUrl = $this->getUtilPhp();
        $tmpUrl = $tmpUrl . "?type=1&url=" . urlencode($url) . "&fileName=" . urlencode($fileName) . "&size=" . urlencode($size);
        return $tmpUrl;
    }

    /**
     * 文件信息
     * @param type $path
     */
    public function fileInfo($path)
    {
        $info = $this->objectMeta($path);
        $info['path'] = $this->getPathOuter($path);
        $info['fileThumb'] = $info['thumbnail'] ? $this->getRefererUrl($info['thumbnail'], $info['name'], $info['size']) : "1";
        logOut("fileInfo:" . json_encode(array(
                "path" => $path,
                "info" => $info
            )));
        return $info;
    }

    /**
     * 文件夹信息
     * @param type $path
     * @return type
     */
    public function folderInfo($path)
    {
        $info = $this->objectMeta($path);
        $info['name'] = $this->pathThis($path);
        $info['path'] = $this->getPathOuter($path);
        logOut("folderInfo:" . json_encode(array(
                "path" => $path,
                "info" => $info
            )));
        return $info;
    }

    /**
     * 获取文件列表
     * https://pan.aliyun.com/union/document/basic#%E8%8E%B7%E5%8F%96%E6%96%87%E4%BB%B6%E5%88%97%E8%A1%A8
     * @param [type] $path
     * @param integer $start
     * @param integer $limit
     * @return void
     */
    public function fileList($path, $start = 0, $limit = 1000, $update = false)
    {
        return file_list($path, $update, $this->rootDir, $this->defaultDriveId, $this->accessToken);
    }

    // 根据path获取缩略图
    private function _thumbUrl($path, $width = 800, $url = '')
    {
        logOut("_thumbUrl:" . json_encode(array('path' => $path, 'width' => $width, 'url' => $url)));
        if (!$url) {
            $info = $this->objectMeta($path);
            if (!$info || !isset($info['thumb_url'])) return '';
            $url = $info['thumb_url'];
        }
        $data = explode('&', $url);
        $size = 'size=';
        foreach ($data as &$val) {
            if (substr($val, 0, strlen($size)) == $size) {
                $val = 'size=c' . $width . '_u' . $width;
            }
        }
        $aaa = implode('&', $data);
        return $aaa;
    }

    /**
     * 列举当前目录下的文件/夹信息
     * @param type $path
     * @return type
     */
    public function listPath($path, $simple = false)
    {
        logOut("listPath:" . json_encode(array(
                "path" => $path,
                "simple" => $simple
            )));
        $start = 0;// 起始位置
        $limit = 1000;
        $folderList = $fileList = array();
        while (true) {
            // 列举文件
            $list = $this->fileList($path, $start, $limit);
            foreach ($list as $info) {
                if ($info['type'] == 'folder') {
                    $folderList[] = $this->folderInfo($path . "/" . $info['name']);
                } else {
                    $fileList[] = $this->fileInfo($path . "/" . $info['name']);
                }
            }
            if (count($list) < $limit) break;
        }
        return array('folderList' => $folderList, 'fileList' => $fileList);
    }

    /**
     * 是否有子文件/夹
     */
    public function has($path, $count = false, $checkFile = true)
    {
        logOut("has:" . json_encode(array('path' => $path, "count" => $count, "checkFile" => $checkFile)));
        $start = 0;// 起始位置
        $limit = 1000;
        $hasFile = 0;
        $hasFolder = 0;
        while (true) {
            // 列举文件
            $list = $this->fileList($path, $start, $limit);
            $total = count($list);

            $data = array_to_keyvalue($list, '', 'isdir');
            $dirs = array_sum($data);
            $file = (count($data) - $dirs);
            if ($count) {
                $hasFolder += $dirs;
                $hasFile += $file;
                if ($total < $limit) break;
                continue;
            }
            if ($checkFile) {
                if ($file) return true;
            } else {
                if ($dirs) return true;
            }
            if ($total < $limit) break;
        }
        if ($count) {
            return array('hasFile' => $hasFile, 'hasFolder' => $hasFolder);
        }
        return false;
    }

    /**
     * 返回所有子项:目录及文件
     * [{"path":"/a/", "folder":1,"size":0},{"path":"/test.txt", "folder":0,"size":"1234"}]
     */
    public function listAll($path)
    {
        logOut("listAll:" . json_encode(array('path' => $path)));
        $start = 0;// 起始位置
        $limit = 1000;
        $limit = 2;
        $result = array();
        while (true) {
            $param = array(
                'path' => '/' . trim($path, '/'),
                'start' => $start,
                'limit' => $limit,
                'recursion' => 1,
            );
            $data = aliRequest($this->rootDir, 'rest/2.0/xpan/multimedia?method=listall', 'GET', $param);
            $start = $data['cursor'];
            foreach ($data['list'] as $item) {
                $result[] = array(
                    'path' => $item['path'],
                    'folder' => $item['isdir'],
                    'size' => isset($item['size']) ? $item['size'] : 0,
                );
            }
            if ($data['has_more'] != '1') break;
        }
        return $result;
    }

    /**
     * 可读
     * @param type $path
     * @return type
     */
    public function canRead($path)
    {
        logOut("canRead:" . json_encode(array('path' => $path)));
        return true;
    }

    /**
     * 可写
     * @param type $path
     * @return type
     */
    public function canWrite($path)
    {
        logOut("canWrite:" . json_encode(array('path' => $path)));
        return true;
    }

    /**
     * 读取内容
     * @param type $file
     * @return type
     */
    public function getContent($file)
    {
        logOut("getContent:" . json_encode(array('file' => $file)));
        return $this->fileSubstr($file, 0, -1);    // 获(截)取全部内容
    }

    /**
     * 写入内容    下载到服务器+写入内容+上传
     * @param type $file
     * @param type $data
     * @return type
     */
    public function setContent($file, $data = '', $repeat = REPEAT_REPLACE)
    {
        logOut("setContent:" . json_encode(array('file' => $file, "data" => $data)));
        // 创建本地临时文件,并上传
        $tempFile = $this->tempFile($this->pathThis($file));
        file_put_contents($tempFile, $data);
        return $this->upload($file, $tempFile);
    }

    /**
     * range
     * https://blog.csdn.net/weixin_34014555/article/details/85863114
     * @param type $path
     * @param type $start
     * @param type $length
     */
    public function fileSubstr($path, $start, $length)
    {
        if($this->gaosuSize != "0"){
            //开启了极速模式
            $info = path2info($this->rootDir, $path);
            if($info && $info['size']){
                if($info['size'] >= ($this->gaosuSize)*1024){
                    logOut("极速模式返回空");
                    return "";
                }
            }
        }
        logOut("fileSubstr:" . json_encode(array('path' => $path, "start" => $start, "length" => $length)));
        $url = $this->getDownUrl(path2id($this->rootDir, $path));
        $content = aliRequest($this->rootDir, $url, false, false, "content");
        return substr($content, $start, $length);
    }

    private function bchexdec($hex)
    {
        $dec = 0;
        $len = strlen($hex);
        for ($i = 1; $i <= $len; $i++) {
            $dec = bcadd($dec, bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i))));
        }
        return $dec;
    }

    private function proof_code($path)
    {
        $n = $this->accessToken;
        $r = $this->bchexdec(substr(md5($n), 0, 16));
        $i = filesize($path);
        $o = $i ? bcmod($r, $i) : 0;
        $file = fopen($path, "r");
        if (fseek($file, $o) == -1) {
            return "";
        }
        $length = 8;
        if ($length > $i - $o) {
            $length = $i - $o;
        }
        $b = fread($file, $length);
        return base64_encode($b);
    }

    private function createJlDir($dirPath)
    {
        $dirPath = str_replace('\\', '/', $dirPath);
        $pathList = explode("/", $dirPath);
        $count = count($pathList);
        $lastPath = "";
        for ($i = 0; $i < $count; $i++) {
            $path = implode("/", array_slice($pathList, 0, $i + 1));
            if (!$path) {
                continue;
            }
            $pid = path2pid($this->rootDir, $path);
            if($pid == "no_this_id"){
                $this->mkdir($lastPath);
            }
            logOut("创建目录:" . json_encode(array(
                    "path" => $path,
                    "pid"=>$pid,
                    "name"=>path2name($path),
                    "lastPath"=>$lastPath
                )));
            $lastPath = $path;

        }
    }

    /**
     * 上传文件
     * @param [type] $destPath
     * @param [type] $localPath
     * @param boolean $moveFile
     * @param [type] $repeat
     * @return void
     */
    public function upload($destPath, $localPath, $moveFile = false, $repeat = REPEAT_REPLACE)
    {
        $size = @filesize($localPath);
        $csha1 = strtoupper(sha1_file($localPath));
        $fl = fenli_name($destPath);
        //$check_name_mode = "overwrite";
        //"moveFile":true,"repeat":"replace" auto_rename
        logOut("upload:" . json_encode(
                array(
                    "destPath" => $destPath,
                    "localPath" => $localPath,
                    "moveFile" => $moveFile,
                    "repeat" => $repeat
                )
            ));
        $this->createJlDir($destPath);
        $pid = path2id($this->rootDir, $fl['parentPath']);
        $param = array(
            "drive_id" => $this->defaultDriveId,
            "part_info_list" => array(
                0 => array(
                    "part_number" => 1
                )
            ),
            "parent_file_id" => $pid,
            "name" => $fl['currentPath'],
            "type" => "file",
            "check_name_mode" => "overwrite",
            "size" => $size,
            "content_hash" => $csha1,
            "content_hash_name" => "sha1",
            "proof_code" => $this->proof_code($localPath),
            "proof_version" => "v1"
        );
        $headers = array("Content-Type: application/json",
            "authorization: Bearer " . $this->accessToken,
            "cache-control: no-cache");
        $res = aliRequest($this->rootDir, "/adrive/v2/file/createWithFolders", $param, $headers);
        if ($res['rapid_upload']) {
            $this->sxLastFile($fl['parentPath']);
            return true;
        }
        aliRequest($this->rootDir, $res['part_info_list'][0]['upload_url']
            , file_get_contents($localPath),
            array("Content-Type:"),
            "putfile"
        );
        $param = array(
            "drive_id" => $this->defaultDriveId,
            "upload_id" => $res['upload_id'],
            "file_id" => $res['file_id']
        );
        $res = aliRequest($this->rootDir, "/v2/file/complete", $param, $headers);
        $this->sxLastFile($fl['parentPath']);
        return $res && $res['file_id'];
    }

    /**
     * 下载    ——单文件
     * @param type $file
     * @param type $destFile
     * @return type
     */
    public function download($file, $destFile)
    {
        logOut("download:" . json_encode(array('file' => $file, 'destFile' => $destFile)));
        $tempFile = IO::getPathInner(IO::mkfile($destFile));
        $handle = fopen($tempFile, 'w');
        $content = $this->getContent($file);
        fwrite($handle, $content);
        fclose($handle);
        return $destFile;
    }

    /**
     * 链接    [image/music/movie/ | download]
     * @param type $path
     * @param type $options // url额外参数
     * @return type
     */
    public function link($path, $options = '')
    {
        logOut("link:" . json_encode(array('path' => $path)));
        $file_name = path2name($path);
        $file_id = path2id($this->rootDir, $path);
        return $this->getRefererUrl($this->getDownUrl($file_id), $file_name, $this->size($path));
    }

    public function fileOut($path, $download = false, $downFilename = false, $etag = '')
    {
        logOut("fileOut:" . json_encode(array(
                'path' => $path,
                'download' => $download,
                'downFilename' => $downFilename,
                'etag' => $etag
            )));
        $link = $this->link($path);
        $this->fileOutLink($link);
    }

    public function fileOutServer($path, $download = false, $downFilename = false, $etag = '')
    {
        logOut("fileOutServer:" . json_encode(array('path' => $path, 'download' => $download, 'downFilename' => $downFilename, 'etag' => $etag)));
        parent::fileOut($path, $download, $downFilename, $etag);
    }

    public function fileOutImage($path, $width = 250)
    {
        logOut("fileOutImage:" . json_encode(array('path' => $path, 'width' => $width)));
        if (!$link = $this->_thumbUrl($path, $width)) {
            $link = $this->link($path, $width);
        }
        // 缩略图 [icon/url1/url2/url3/url4]
        if (is_array($link)) {
            $link = $this->_thumbUrl($path, $width, $link['url1']);
        }
        $this->fileOutLink($link);
    }

    // 后端输出图片
    public function fileOutImageServer($path, $width = 250)
    {
        logOut("fileOutImageServer:" . json_encode(array('path' => $path, 'width' => $width)));
        if ($link = $this->_thumbUrl($path, $width)) {
            $this->fileOutLink($link);
        }
        parent::fileOutImage($path, $width);
    }

    /**
     * 文件MD5
     * @param type $path
     * @return boolean
     */
    public function hashMd5($path)
    {
        logOut("hashMd5:" . json_encode(array('path' => $path)));
        $info = $this->objectMeta($path);
        //return isset($info['content_hash']) ? $info['content_hash'] : false;	// 文件类型才有md5
        return false;
    }

    public function size($file)
    {
        $info = $this->objectMeta($file);
        return $info ? $info['size'] : 0;
    }

    public function info($path)
    {
        if ($this->isFolder($path)) {
            return $this->folderInfo($path);
        } else if ($this->isFile($path)) {
            return $this->fileInfo($path);
        }
        return false;
    }

    public function exist($path)
    {
        logOut("exist:" . json_encode(array('path' => $path)));
        return $this->isFile($path) || $this->isFolder($path);
    }

    public function isFile($path)
    {
        $flag = !$this->isFolder($path) && $this->objectMeta($path)['name'];
        logOut("isFile:" . json_encode(array('path' => $path, 'flag' => $flag)));
        return $flag;
    }

    public function isFolder($path)
    {
        return $this->cacheMethod('_isFolder', $path);
    }

    protected function objectMeta($path)
    {
        return path2info($this->rootDir, $path);
    }

    protected function _isFolder($path)
    {
        if ($path == '' || $path == '/') return true;
        $info = $this->objectMeta($path);
        return (isset($info['type']) && $info['type'] == 'folder') ? true : false;
    }
}
